/*
 This file is part of Subsonic.

 Subsonic is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 Subsonic is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with Subsonic.  If not, see <http://www.gnu.org/licenses/>.

 Copyright 2009 (C) Sindre Mehus
 */
package net.nullsum.audinaut.util;

import android.content.SharedPreferences;
import android.util.Log;

import net.nullsum.audinaut.domain.MusicDirectory;
import net.nullsum.audinaut.service.DownloadService;
import net.nullsum.audinaut.service.MusicService;
import net.nullsum.audinaut.service.MusicServiceFactory;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * @author Sindre Mehus
 * @version $Id$
 */
public class ShufflePlayBuffer {

    private static final String TAG = ShufflePlayBuffer.class.getSimpleName();
    private static final String CACHE_FILENAME = "shuffleBuffer.ser";
    private final ArrayList<MusicDirectory.Entry> buffer = new ArrayList<>();
    private final Runnable runnable;
    private final DownloadService context;
    private final int refillThreshold;
    private ScheduledExecutorService executorService;
    private boolean firstRun = true;
    private int lastCount = -1;
    private boolean awaitingResults = false;
    private int capacity;
    private SharedPreferences.OnSharedPreferenceChangeListener listener;
    private int currentServer;
    private String currentFolder = "";
    private String genre = "";
    private String startYear = "";
    private String endYear = "";

    public ShufflePlayBuffer(DownloadService context) {
        this.context = context;

        executorService = Executors.newSingleThreadScheduledExecutor();
        runnable = this::refill;
        executorService.scheduleWithFixedDelay(runnable, 1, 10, TimeUnit.SECONDS);

        // Calculate out the capacity and refill threshold based on the user's random size preference
        int shuffleListSize = Math.max(1, Integer.parseInt(Util.getPreferences(context).getString(Constants.PREFERENCES_KEY_RANDOM_SIZE, "20")));
        // ex: default 20 -> 50
        capacity = shuffleListSize * 5 / 2;
        capacity = Math.min(500, capacity);

        // ex: default 20 -> 40
        refillThreshold = capacity * 4 / 5;
    }

    public List<MusicDirectory.Entry> get(int size) {
        clearBufferIfnecessary();
        // Make sure fetcher is running if needed
        restart();

        List<MusicDirectory.Entry> result = new ArrayList<>(size);
        synchronized (buffer) {
            boolean removed = false;
            while (!buffer.isEmpty() && result.size() < size) {
                result.add(buffer.remove(buffer.size() - 1));
                removed = true;
            }

            // Re-cache if anything is taken out
            if (removed) {
                FileUtil.serialize(context, buffer, CACHE_FILENAME);
            }
        }
        Log.i(TAG, "Taking " + result.size() + " songs from shuffle play buffer. " + buffer.size() + " remaining.");
        if (result.isEmpty()) {
            awaitingResults = true;
        }
        return result;
    }

    public void shutdown() {
        executorService.shutdown();
        Util.getPreferences(context).unregisterOnSharedPreferenceChangeListener(listener);
    }

    private void restart() {
        synchronized (buffer) {
            if (buffer.size() <= refillThreshold && lastCount != 0 && executorService.isShutdown()) {
                executorService = Executors.newSingleThreadScheduledExecutor();
                executorService.scheduleWithFixedDelay(runnable, 0, 10, TimeUnit.SECONDS);
            }
        }
    }

    private void refill() {
        // Check if active server has changed.
        clearBufferIfnecessary();

        if (buffer != null && (buffer.size() > refillThreshold || (!Util.isNetworkConnected(context) && !Util.isOffline(context)) || lastCount == 0)) {
            executorService.shutdown();
            return;
        }

        try {
            MusicService service = MusicServiceFactory.getMusicService(context);

            // Get capacity based
            int n = capacity - buffer.size();
            MusicDirectory songs = service.getRandomSongs(n, null, genre, startYear, endYear, context, null);

            synchronized (buffer) {
                lastCount = 0;
                for (MusicDirectory.Entry entry : songs.getChildren()) {
                    if (!buffer.contains(entry)) {
                        buffer.add(entry);
                        lastCount++;
                    }
                }
                Log.i(TAG, "Refilled shuffle play buffer with " + lastCount + " songs.");

                // Cache buffer
                FileUtil.serialize(context, buffer, CACHE_FILENAME);
            }
        } catch (Exception x) {
            // Give it one more try before quitting
            if (lastCount != -2) {
                lastCount = -2;
            } else if (lastCount == -2) {
                lastCount = 0;
            }
            Log.w(TAG, "Failed to refill shuffle play buffer.", x);
        }

        if (awaitingResults) {
            awaitingResults = false;
            context.checkDownloads();
        }
    }

    private void clearBufferIfnecessary() {
        synchronized (buffer) {
            final SharedPreferences prefs = Util.getPreferences(context);
            if (currentServer != Util.getActiveServer(context)
                    || !Util.equals(currentFolder, Util.getSelectedMusicFolderId(context))
                    || (genre != null && !genre.equals(prefs.getString(Constants.PREFERENCES_KEY_SHUFFLE_GENRE, "")))
                    || (startYear != null && !startYear.equals(prefs.getString(Constants.PREFERENCES_KEY_SHUFFLE_START_YEAR, "")))
                    || (endYear != null && !endYear.equals(prefs.getString(Constants.PREFERENCES_KEY_SHUFFLE_END_YEAR, "")))) {
                lastCount = -1;
                currentServer = Util.getActiveServer(context);
                currentFolder = Util.getSelectedMusicFolderId(context);
                genre = prefs.getString(Constants.PREFERENCES_KEY_SHUFFLE_GENRE, "");
                startYear = prefs.getString(Constants.PREFERENCES_KEY_SHUFFLE_START_YEAR, "");
                endYear = prefs.getString(Constants.PREFERENCES_KEY_SHUFFLE_END_YEAR, "");
                buffer.clear();

                if (firstRun) {
                    ArrayList cacheList = FileUtil.deserialize(context, CACHE_FILENAME, ArrayList.class);
                    if (cacheList != null) {
                        buffer.addAll(cacheList);
                    }

                    listener = (prefs1, key) -> {
                        clearBufferIfnecessary();
                        restart();
                    };
                    prefs.registerOnSharedPreferenceChangeListener(listener);
                    firstRun = false;
                } else {
                    // Clear cache
                    File file = new File(context.getCacheDir(), CACHE_FILENAME);
                    file.delete();
                }
            }
        }
    }
}
